/**
 * @file STL_Mqtt.h
 * @author Derek Liu (yjkhtddx@sina.com)
 * @brief 
 * @version 0.1
 * @date 2020-09-23
 * 
 * @copyright Copyright Sci-Tech Laboratories (c) 2020
 * 
 */

#ifndef _STL_MQTT_H_
#define _STL_MQTT_H_

#include <string>
#include <thread>
#include <mutex>
#include <map>
#include <set>
#include <functional>
#include <mosquittopp/mosquittopp.h>
#include "STL_Mqtt.h"

namespace STL
{

    class STL_MqttDelegate;
    class STL_Mqtt
    {
    public:
        typedef enum Status_e
        {
            None,
            Init,
            Connecting,
            Connected,
            Disconnected,
            Over
        } Status;
        typedef struct Message_t
        {
            std::string topic;
            std::string payload;
            int mid;
            int qos;
            bool retain;
            Message_t() = default;
            Message_t(std::string topic, std::string payload)
            {
                this->topic = topic;
                this->payload = payload;
                this->mid = -1;
                this->qos = 0;
                this->retain = false;
            };
            Message_t(std::string topic, std::string payload, int mid)
            {
                this->topic = topic;
                this->payload = payload;
                this->mid = mid;
                this->qos = 0;
                this->retain = false;
            };
        } Message;
        typedef struct Option_t
        {
            std::string ProfileName;
            std::string ProfileType;
            // MQTT Broker Profile Settings
            std::string BrokerAddress;
            uint16_t BrokerPort;
            std::string ClientID;
            // General
            uint32_t ConnectionTimeout; //TODO 使用
            uint32_t KeepAliveInterval; //TODO 使用
            bool CleanSession;
            bool AutoReconnect;      //TODO 使用
            uint32_t MaxInflight;    //TODO 使用
            std::string MQTTVersion; //TODO 使用
            // SSL/STL
            bool IsEnableTLS;       //激活加密连接
            std::string CAFilePath; //CA证书路径
            bool IsVerifyPeer;      //是否验证服务器端
            // UserCredentials
            std::string Username;
            std::string Password;
            Option_t()
            {
                this->ProfileName = "GBA9300";
                this->ProfileType = "MQTT Broker";
                this->ConnectionTimeout = 30;
                this->KeepAliveInterval = 60;
                this->CleanSession = true;
                this->AutoReconnect = false;
                this->MaxInflight = 10;
                this->MQTTVersion = "3.1.1";
            };
            Option_t(std::string BrokerAddress, uint16_t BrokerPort, std::string ClientID, std::string Username, std::string Password)
            {
                this->ProfileName = "GBA9300";
                this->ProfileType = "MQTT Broker";
                this->BrokerAddress = BrokerAddress;
                this->BrokerPort = BrokerPort;
                this->ClientID = ClientID;
                this->ConnectionTimeout = 30;
                this->KeepAliveInterval = 60;
                this->CleanSession = true;
                this->AutoReconnect = false;
                this->MaxInflight = 10;
                this->MQTTVersion = "3.1.1";
                this->Username = Username;
                this->Password = Password;
                this->IsEnableTLS = false;
                this->CAFilePath = "./config/cacert.pem"; //下载自 https://curl.se/ca/cacert.pem
                this->IsVerifyPeer = true;
            };
        } Option;
        typedef std::function<void(STL_Mqtt::Status /*status*/)> StatusCallback;
        typedef std::function<void(STL_Mqtt::Message /*msg*/)> MessageCallback;
        typedef std::function<void(std::string /*log*/)> LogCallback;
        typedef std::function<void()> ConnectCallback;
        typedef std::function<void()> DisConnectCallback;
        // typedef std::function<void(std::string)> SubscribeCallback;

    public:
        STL_Mqtt(STL_Mqtt::Option);
        ~STL_Mqtt() = default;
        int start();
        int stop();
        STL_Mqtt::Status getStatus() { return m_status; };

        void setStatusCallback(STL_Mqtt::StatusCallback callback) { m_statusCallback = callback; };
        void setLogCallback(STL_Mqtt::LogCallback callback) { m_logCallback = callback; };
        // void setSubscribeCallback(STL_Mqtt::SubscribeCallback callback) { m_subscribeCallback = callback; };
        void setConnectCallback(STL_Mqtt::ConnectCallback callback) { m_ConnectCallback = callback; };
        void setDisConnectCallback(STL_Mqtt::DisConnectCallback callback) { m_DisConnectCallback = callback; };
        int subscribe(std::string topic, STL_MqttDelegate *pDelegate = nullptr)
        {
            return subscribe(topic, 0, pDelegate);
        };

        int initSubscribe();
        int subscribe(std::string topic, int qos, STL_MqttDelegate *pDelegate = nullptr);
        int unsubscribe(std::string topic, STL_MqttDelegate *pDelegate = nullptr);
        int publishMessage(STL_Mqtt::Message message);

        void OnMqttMessage(STL::STL_Mqtt::Message message);

    private:
        STL_Mqtt::LogCallback m_logCallback;
        STL_Mqtt::ConnectCallback m_ConnectCallback;
        STL_Mqtt::DisConnectCallback m_DisConnectCallback;

    private:
        STL_Mqtt::StatusCallback m_statusCallback;

        // STL_Mqtt::SubscribeCallback m_subscribeCallback;
        void setStatus(STL_Mqtt::Status status);

    private:
        int m_index;
        bool m_run;
        std::thread m_thread;
        std::mutex m_mutex;
        void mqttThread();

    private:
        STL_Mqtt::Option m_option;
        STL_Mqtt::Status m_status;
        static int s_SubscribeMessageIndex;
        std::mutex m_subscribeMapMutex;
        std::map<std::string, std::set<STL_MqttDelegate *> /**/> m_subscribeMap;
        struct mosquitto *m_pMqtt_st;

    public:
        void printMessage(STL_Mqtt::Message message, std::string tag = "Message");
        void modulelog_debug(std::string log);
        void modulelog_warn(std::string log);

    private:
        static void mqttLogCallback(struct mosquitto *mosq, void *userdata, int level, const char *str);
        static void mqttConnectCallback(struct mosquitto *mosq, void *userdata, int result);
        static void mqttMessageCallback(struct mosquitto *mosq, void *userdata, const struct mosquitto_message *message);
        static void mqttSubscribeCallback(struct mosquitto *mosq, void *userdata, int mid, int qos_count, const int *granted_qos);
        static void mqttDisConnectCallback(struct mosquitto *mosq, void *userdata, int result);
        static void mqttUnsubscribeCallback(struct mosquitto *mosq, void *userdata, int mid);

        static void AsyncMqttMessageCallback(STL_MqttDelegate *target, STL::STL_Mqtt::Message msg);

    public:
        static std::mutex s_init_mutex;
        static void initlib();
        static std::string toLowerHex(std::string str, size_t offset = 0, size_t max = 0);
        static std::string getMessageLog(STL_Mqtt::Message message, std::string tag = "Message");
    };
    class STL_MqttDelegate //委派
    {
    public:
        virtual void OnMqttMessage(STL_Mqtt::Message msg) = 0;
    };
} // namespace STL

#endif //_STL_MQTT_H_